using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ESG.Entities;
using Sustainalytics.ScreeningTool.Synchronization.Caches;
using Sustainalytics.ScreeningTool.Synchronization.Entities;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ScreeningTool.Synchronization.Handlers.ESG
{
    public class RPCompanyESGComputationResultHandler : IHandler<RPCompanyESGComputationResult>
    {
        private readonly IndicatorTemplatesCache _indicatorTemplatesCache;
        private readonly ControversyTemplatesCache _controversyTemplatesCache;
        private readonly ResearchStatesCache _researchStatesCache;

        public RPCompanyESGComputationResultHandler(
            IndicatorTemplatesCache indicatorTemplatesCache,
            ControversyTemplatesCache controversyTemplatesCache,
            ResearchStatesCache researchStatesCache)
        {
            _indicatorTemplatesCache = indicatorTemplatesCache;
            _controversyTemplatesCache = controversyTemplatesCache;
            _researchStatesCache = researchStatesCache;
        }

        public ProjectionDefinition<RPCompanyESGComputationResult> GetProjection(ProjectionDefinitionBuilder<RPCompanyESGComputationResult> builder) => builder
            .Include(x => x.Indicators)
            .Include(x => x.DisclosureScore.Score)
            .Include(x => x.PreparednessScore.Score)
            .Include(x => x.QualitativePerformanceScore.Score)
            .Include(x => x.QuantitativePerformanceScore.Score)
            .Include(x => x.OverallPerformance)
            .Include(x => x.EnvironmentPerformance)
            .Include(x => x.SocialPerformance)
            .Include(x => x.GovernancePerformance);

        public IEnumerable<ExtraElement> GetExtraElements(RPCompanyESGComputationResult entity)
            => EnumerableExtensions.Concat(
                entity.GetExtraElements(),
                entity.OverallPerformance?.GetExtraElements(),
                entity.EnvironmentPerformance?.GetExtraElements("Environment"),
                entity.SocialPerformance?.GetExtraElements("Social"),
                entity.GovernancePerformance?.GetExtraElements("Governance"),
                GetContinuityRatingExtraElements(entity),
                GetType8ExtraElements(entity));

        public IEnumerable<ExtraElement> GetType8ExtraElements(RPCompanyESGComputationResult entity)
        {
            var controversyTemplates = _controversyTemplatesCache[entity.Id];

            return entity.Indicators
                .Where(x => controversyTemplates.Contains(x.Code))
                .SelectMany(x =>
                {
                    var key = $"{x.Code} {_indicatorTemplatesCache[x.Code]}";
                    return new ExtraElementList
                    {
                        { $"{key}-Weight",          x.Weight              },
                        { $"{key}-Weighted Score",  x.WeightedScore       },
                    };
                });
        }

        public IEnumerable<ExtraElement> GetContinuityRatingExtraElements(RPCompanyESGComputationResult entity)
        {
            var companyId = entity.Id;
            var research = _researchStatesCache.GetResearchStates(companyId);
            var controversyTemplates = _controversyTemplatesCache[entity.Id];

            if (research == null)
                return new ExtraElementList();

            if (research.ComprehensiveEsg.IsAvailable() && research.CoreEsg.IsAvailable())
                return new ExtraElementList();

            return entity.Indicators
                .Where(x => !controversyTemplates.Contains(x.Code))
                .SelectMany(x =>
                {
                    var key = $"{x.Code} {_indicatorTemplatesCache[x.Code]}";

                    if (research.ComprehensiveEsg.IsAvailable())
                    {
                        return new ExtraElementList
                        {
                            { $"{key}-Answer Category", x.SelectedAnswer?.Text               },
                            { $"{key}-Raw Score",       x.RawScore.Bind(value => (int)value) },
                            { $"{key}-Weight",          x.Weight?.Round(4)                   },
                            { $"{key}-Weighted Score",  x.WeightedScore?.Round(4)            }
                        };
                    }
                    if (research.CoreEsg.IsAvailable())
                    {
                        return new ExtraElementList
                        {
                            { $"{key}-Answer Category", x.SelectedAnswer?.Text               },
                            { $"{key}-Raw Score",       x.RawScore.Bind(value => (int)value) },
                        };
                    }

                    return Enumerable.Empty<ExtraElement>();
                });
        }

    }

    public static class RPCompanyESGComputationResultExtensions
    {
        public static IEnumerable<ExtraElement> GetExtraElements(this RPCompanyESGComputationResult x)
            => new ExtraElementList
            {
                { "Disclosure Score",               x.DisclosureScore?.Score.Round()              },
                { "Preparedness Score",             x.PreparednessScore?.Score.Round()            },
                { "Qualitative Performance Score",  x.QualitativePerformanceScore?.Score.Round()  },
                { "Quantitative Performance Score", x.QuantitativePerformanceScore?.Score.Round() },
            };
     }

    public static class RPPerformanceExtensions
    {
        public static IEnumerable<ExtraElement> GetExtraElements(this RPPerformance x)
            => new ExtraElementList
            {
                { "Comparative Group Average",   x.Avg.Round(2)                                                      },
                { "Percentile",                  x.Percentile.Round(2)                                               },
                { "Rank",                        x.GetPeerRanking()                                                  },
                { "Total ESG Score",             x.Score.Round(2)                                                    },
                { "ESG Performance Assessment",  x.Assessment?.Name,                          x.AssessmentTimestamp  },
                { "Momentum Overall Score",      x.ScoreMomentumYoY?.DataPoint.Round(2),      x.Score.Timestamp      },
                { "Momentum Overall Percentile", x.PercentileMomentumYoY?.DataPoint.Round(2), x.Percentile.Timestamp },
            };

        public static IEnumerable<ExtraElement> GetExtraElements(this RPPerformance x, string research)
            => new ExtraElementList
            {
                { $"Comparative Group Average {research}", x.Avg.Round(2)                                                      },
                { $"{research} Percentile",                x.Percentile.Round(2)                                               },
                { $"{research} Rank",                      x.GetPeerRanking()                                                  },
                { $"{research} Score",                     x.Score.Round(2)                                                    },
                { $"{research} Weight",                    x.Weight.Round(2)                                                   },
                { $"{research} performance assessment",    x.Assessment?.Name,                          x.AssessmentTimestamp  },
                { $"Momentum {research} Score",            x.ScoreMomentumYoY?.DataPoint.Round(2),      x.Score.Timestamp      },
                { $"Momentum {research} Percentile",       x.PercentileMomentumYoY?.DataPoint.Round(2), x.Percentile.Timestamp },
            };

        public static Timestamped<PeerRanking> GetPeerRanking(this RPPerformance rpPerformance)
        {
            if (rpPerformance.Rank?.Value == null)
                return null;

            return new Timestamped<PeerRanking>
            {
                Value = new PeerRanking(rpPerformance.Rank.Value.Value, rpPerformance.GroupCount),
                Timestamp = new[] { rpPerformance.Rank.Timestamp, rpPerformance.GroupCount.Timestamp }.Max(),
            };
        }
    }

}
